/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2025 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.disablers

import net.ccbluex.liquidbounce.config.types.nesting.ToggleableConfigurable
import net.ccbluex.liquidbounce.event.events.PacketEvent
import net.ccbluex.liquidbounce.event.events.QueuePacketEvent
import net.ccbluex.liquidbounce.event.events.TransferOrigin
import net.ccbluex.liquidbounce.event.events.WorldChangeEvent
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.features.module.modules.exploit.disabler.ModuleDisabler
import net.ccbluex.liquidbounce.utils.client.PacketQueueManager
import net.ccbluex.liquidbounce.utils.client.asPlainText
import net.ccbluex.liquidbounce.utils.client.asText
import net.ccbluex.liquidbounce.utils.client.chat
import net.ccbluex.liquidbounce.utils.client.sendPacketSilently
import net.ccbluex.liquidbounce.utils.kotlin.EventPriorityConvention
import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket
import net.minecraft.network.packet.c2s.common.KeepAliveC2SPacket
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket
import net.minecraft.network.packet.c2s.play.PlayerInteractBlockC2SPacket
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket
import net.minecraft.network.packet.c2s.play.TeleportConfirmC2SPacket
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket
import net.minecraft.util.Formatting
import net.minecraft.util.hit.BlockHitResult

/**
 * Attempt at a Verus disabler, originally aimed at BlocksMC.
 * Instead of taking known disabler methods, this was made by referencing the Verus source code
 * (which should be 1:1 with the build BlocksMC is running) and looking for exemptions.
 *
 * A bit of credit to angles/port for tips of how older disablers worked.
 */
internal object DisablerVerusExperimental : ToggleableConfigurable(ModuleDisabler, "VerusExperimental", false) {

    private val worldChangeWait by int("WorldChangeWait", 15000, 0..30000, "ms")
    private val delayTime by int("DelayEndTime", 14000, 0..30000, "ms")
    private val positionDelayTime by int("DelayBeginTime", 13800, 0..30000, "ms")

    private val check14E by boolean("Scaffold14E", true)

    // Disables in-game check
    override val running: Boolean
        get() = ModuleDisabler.enabled && this.enabled

    object NoAction : ToggleableConfigurable(DisablerVerusExperimental, "NoAction", false) {

        private val sprinting by boolean("Sprinting", true)

        val packetHandler = handler<PacketEvent> { event ->
            if (event.packet is ClientCommandC2SPacket) {
                when (event.packet.mode) {

                    ClientCommandC2SPacket.Mode.START_SPRINTING, ClientCommandC2SPacket.Mode.STOP_SPRINTING -> {
                        if (sprinting) {
                            event.cancelEvent()
                        }
                    }

                    else -> {}

                }
            }
        }

    }

    init {
        tree(VoidTP)
        tree(NoAction)
    }

    object VoidTP : ToggleableConfigurable(DisablerVerusExperimental, "VoidTP", false) {

        private val voidTPDelay by int("VoidTPDelay", 1000, 0..30000, "ms")

        private val waitForGround by boolean("WaitForGround", true)

        private var lastVoidTP = 0L
        private var cancelNext = 0
        private var lastTeleportId = 0

        val packetHandlerVoidTp = handler<PacketEvent> { event ->
            if (player.age <= 20) return@handler

            if (event.packet is PlayerMoveC2SPacket) {
                if (player.y > -64) {

                    if (lastVoidTP + voidTPDelay < System.currentTimeMillis() &&
                        (!waitForGround || player.isOnGround)) {
                        lastVoidTP = System.currentTimeMillis()

                        sendPacketSilently(
                            PlayerMoveC2SPacket.Full(
                                player.x,
                                -48.0,
                                player.z,
                                player.yaw,
                                player.pitch,
                                true,
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(
                            PlayerMoveC2SPacket.Full(
                                player.lastX,
                                player.lastYClient,
                                player.lastZ,
                                player.lastYaw,
                                player.lastPitch,
                                false,
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(
                            PlayerMoveC2SPacket.Full(
                                player.x,
                                player.y,
                                player.z,
                                player.yaw,
                                player.pitch,
                                player.isOnGround,
                                player.horizontalCollision
                            )
                        )

                        sendPacketSilently(TeleportConfirmC2SPacket(lastTeleportId))
                        chat("Teleport confirmed".asPlainText(Formatting.GREEN))
                        event.cancelEvent()
                        cancelNext = 2
                    }

                }
            }

            if (event.packet is PlayerPositionLookS2CPacket) {
                chat("Position reset received".asPlainText(Formatting.RED))
                if (cancelNext <= 0) {
                    return@handler
                }
                cancelNext--

                event.cancelEvent()
                lastTeleportId = event.packet.teleportId

                chat("Position reset cancelled".asPlainText(Formatting.GREEN))
            }
        }

    }

    private var lastFlush = 0L

    private var waitTime = 0L

    private var lastDiff = -1L
    private var lastPing = 1L

    @Suppress("unused")
    private val worldChangeHandler = handler<WorldChangeEvent> {
        waitTime = System.currentTimeMillis() + worldChangeWait
    }

    @Suppress("unused")
    private val packetHandler = handler<PacketEvent> { event ->
        when (val packet = event.packet) {
            is PlayerInteractBlockC2SPacket -> {
                if (check14E) {
                    sendPacketSilently(
                        PlayerInteractBlockC2SPacket(
                            packet.hand, BlockHitResult(
                                packet.blockHitResult.blockPos.toCenterPos(),
                                packet.blockHitResult.side,
                                packet.blockHitResult.blockPos,
                                packet.blockHitResult.isInsideBlock
                            ), packet.sequence
                        )
                    )

                    event.cancelEvent()
                }
            }
        }
    }

    @Suppress("unused")
    private val fakeLagHandler = handler<QueuePacketEvent>(
        priority = EventPriorityConvention.SAFETY_FEATURE
    ) { event ->
        if (event.origin != TransferOrigin.OUTGOING || waitTime > System.currentTimeMillis()) {
            return@handler
        }

        val packet = event.packet

        event.action = when {
            packet == null && lastFlush + delayTime < System.currentTimeMillis() -> {
                lastFlush = System.currentTimeMillis()
                PacketQueueManager.Action.QUEUE
            }

            packet is KeepAliveC2SPacket -> {
                if (lastDiff == -1L) {
                    lastDiff = packet.id
                } else {
                    if (packet.id != lastPing) {
                        lastPing = lastDiff
                        lastDiff = packet.id
                        packet.id = lastPing
                    }
                }

                PacketQueueManager.Action.QUEUE
            }

            packet is CommonPongC2SPacket -> {
                PacketQueueManager.Action.QUEUE
            }

            else -> return@handler
        }

    }

    @Suppress("unused")
    private val flushHandler = handler<QueuePacketEvent>(
        priority = EventPriorityConvention.OBJECTION_AGAINST_EVERYTHING
    ) { event ->
        val packet = event.packet

        if (waitTime > System.currentTimeMillis() || event.origin != TransferOrigin.OUTGOING
            || packet !is PlayerMoveC2SPacket) {
            return@handler
        }

        if (lastFlush + positionDelayTime < System.currentTimeMillis()) {
            event.action = PacketQueueManager.Action.QUEUE
        }
    }

}
